package com.ee.bruscar.generator;


import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

public class InstanceTrawler
{

    private ArrayList<Object> foundObjects;
   
    @SuppressWarnings("unchecked")
    public ArrayList<Object> getAllInstancesOfType( Object source,
            Class clazz )
    {
        foundObjects = new ArrayList<Object>();
       
        if( source instanceof List )
        {
            List list = (List) source;
            for( Object sourceObject : list )
            {
                recursivelyGetAllInstancesOfType( sourceObject, clazz);
            }
            return foundObjects;
        }
        else
        {
            return recursivelyGetAllInstancesOfType( source, clazz);
        }
    }
   
    @SuppressWarnings("unchecked")
    private ArrayList<Object> recursivelyGetAllInstancesOfType( Object source, Class typeToRetrieve )
    {
       
        if( typeToRetrieve.isInstance( source ) )
        {
            foundObjects.add( source );
        }
       
        ArrayList<Method> getters = getBeanGetters( source.getClass() );
        Iterator<Method> iter = getters.iterator();
       
        while( iter.hasNext() )
        {
            Method getterMethod = iter.next();
           
            Class returnType = getterMethod.getReturnType();
           
            if( returnType == typeToRetrieve )
            {
                addTypeToFoundObjects(source, getterMethod);
            }
            else if( isASuperClass( returnType , typeToRetrieve ) )
            {
                try
                {
                    Object getterResult = invokeGetter( getterMethod, source);
                   
                    if( getterResult != null &&
                            getterResult.getClass() == typeToRetrieve )
                    {
                        foundObjects.add( getterResult );
                    }
                   
                }
                catch (Exception e) {
                    throw new RuntimeException( "Exception getting an object from a super \n" + e.getMessage() );
                }
            }
            else if( getterMethod.getReturnType() == ArrayList.class )
            {
                getAllInstancesOfTypeFromList(source, typeToRetrieve, getterMethod);
            }
            else if( isComplexObject( getterMethod ) )
            {
                if( getObjectFromInvoke( source, getterMethod ) != null )
                {
                    Object complexObject = getObjectFromInvoke(source, getterMethod);
                    recursivelyGetAllInstancesOfType(complexObject, typeToRetrieve );
                }
            }
           
        }
        return foundObjects;
    }
   
    @SuppressWarnings("unchecked")
    private boolean isASuperClass(Class superClassToFind, Class subClassToInspect)
    {
        Class foundSuperClass = subClassToInspect.getSuperclass();
       
        if( foundSuperClass == null )
        {
            return false;
        }
        if( foundSuperClass == superClassToFind )
        {
            return true;
        }
        else
        {
            return isASuperClass(superClassToFind, foundSuperClass);
        }
    }
   

    private Object getObjectFromInvoke( Object source, Method getter )
    {
        Object returnObject;
        try
        {
            returnObject = getter.invoke( source );
            return returnObject;
        }
        catch ( Exception e )
        {
            return null;
        }
    }
   
    public ArrayList<Method> getBeanGetters( Class clazz )
    {
        ArrayList<Method> methodList = new ArrayList<Method>();
       
        recursivelyAddMethodsFromClass( clazz, methodList );
       
        return methodList;
    }
   
    private void recursivelyAddMethodsFromClass( Class<?> clazz, ArrayList<Method> methodList )
    {
        Field[] declaredFields = clazz.getDeclaredFields();
       
        for (int i = 0; i < declaredFields.length; i++)
        {
            Field field = declaredFields[i];
           
            if( getGetterFor( clazz, field ) != null )
            {
                methodList.add( getGetterFor(clazz, field ) );
            }
        }
       
        if( clazz.getSuperclass() != null )
        {
            recursivelyAddMethodsFromClass( clazz.getSuperclass(), methodList );
        }
       
        return;
    }
   
    private Method getGetterFor( Class<?> clazz, Field field )
    {
        Method accessor;
        try
        {
            String fieldName = field.getName();
            String prefix = field.getType().getName().equals("boolean") ? "is" : "get";
            accessor = clazz.getDeclaredMethod( getGetterName( prefix, fieldName ) );
        }
        catch (NoSuchMethodException e)
        {
            return null;
        }
       
        return accessor;
    }
   
    private String getGetterName( String prefix, String fieldName )
    {
        String firstCharacter = fieldName.substring(0,1);
        String remainder = fieldName.substring(1, fieldName.length() );
        return prefix + firstCharacter.toUpperCase() + remainder;
       
    }
   
    @SuppressWarnings({ "unchecked", "unused" })
    private ArrayList<Method> getMethodsThatReturn( Object source, Class returnType )
    {
        ArrayList<Method> returnTypeGetters = new ArrayList<Method>();
       
        ArrayList<Method> getters = getBeanGetters( source.getClass() );
        Iterator<Method> iter = getters.iterator();
       
        while( iter.hasNext() )
        {
            Method getter = iter.next();
            if( getter.getReturnType() == returnType )
            {
                returnTypeGetters.add( getter );
            }
        }
        return returnTypeGetters;
    }
   
   

    private boolean isComplexObject(Method method)
    {
        return !isPrimitive( method.getReturnType() )
                && !method.getReturnType().getPackage().getName().contains("java.lang");
    }

    @SuppressWarnings("unchecked")
    private void getAllInstancesOfTypeFromList(
                Object source,
                Class clazz,
                Method method)
    {
        ArrayList<Object> list;
        try
        {
            list = (ArrayList<Object>) method.invoke( source );
            if( list == null )
            {
                return;
            }
            Iterator<Object> objectIterator = list.iterator();
            while( objectIterator.hasNext() )
            {
                Object item = objectIterator.next();
                if( item.getClass() == clazz )
                {
                    foundObjects.add( item );
                }
                else
                {
                    recursivelyGetAllInstancesOfType(item, clazz );
                }
            }
        }
        catch ( Exception e )
        {
            String errorMessage = "Error getting Items from list\n";
            throw new RuntimeException( errorMessage + e.getMessage() );
        }
    }

    private void addTypeToFoundObjects(Object source, Method method)
    {
        try
        {
            foundObjects.add( invokeGetter( method, source) );
        }
        catch( Exception e )
        {
            e.printStackTrace();
        }
    }

    @SuppressWarnings("unchecked")
    public boolean isPrimitive( Class type )
    {
        if( type.getName().equals("boolean"))
        {
            return true;
        }
        if( type.getName().equals("int"))
        {
            return true;
        }
        if( type.getName().equals("long"))
        {
            return true;
        }
        if( type.getName().equals("double"))
        {
            return true;
        }
        if( type == java.lang.annotation.Annotation.class )
        {
            return true;
        }
        if( type == String.class )
        {
            return true;
        }
        if( type == Integer.class )
        {
            return true;
        }
        if( type == Boolean.class )
        {
            return true;
        }
        if( type == Date.class )
        {
            return true;
        }
        return false;
    }
   
    private Object invokeGetter(Method method, Object source) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException
    {
        return method.invoke(source);
    }

}
